"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '15/04/2024'
    comment: tornado app初始化
"""
import types
from uuid import uuid4
import traceback
import nest_asyncio
from fastapi import APIRouter, FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import RequestValidationError
from fastapi.openapi.docs import (
    get_redoc_html,
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
from starlette.middleware import Middleware
from starlette_context import plugins
from starlette_context.middleware import RawContextMiddleware
from fastapi.routing import APIRoute
from laner.pydantic import BaseModel

from project.static import STATIC_FOLDER
from project.core.function import get_models, json_response
from project.lib.decorator import api_common
from project.lib.common import logger, logger_abnormal
from project.lib.validators import PydanticErrors
from project.lib.common import api_param_map, ApiParam
from fastapi.dependencies.models import ModelField, Dependant

MIDDLEWARE = [
    Middleware(
        RawContextMiddleware,
        plugins=(plugins.RequestIdPlugin(), plugins.CorrelationIdPlugin()),
    )
]


def init_app():
    """
    :return:
    """
    app = FastAPI(
        title="Venus+Adonis",
        description="接口文档",
        version='0.1.0',
        openapi_url=f"/data_manger.json",
        middleware=MIDDLEWARE,
        swagger_ui_oauth2_redirect_url='/docs/oauth2-redirect',
        swagger_ui_init_oauth={
            "clientId": "your-client-id",
            "clientSecret": "your-client-secret",
            "realm": "your-realm",
            "appName": "FastAPI SwaggerUI",
            "scopeSeparator": " ",
            "additionalQueryStringParams": {"foo": "bar"},
            "usePkceWithAuthorizationCodeGrant": True,
        }
    )
    app.docs_url = f"/docs"
    app.redoc_url = f"/redoc"
    app.swagger_ui_oauth2_redirect_url = f"/docs/oauth2-redirect"
    app.openapi_tags = [
        {"name": "Basic"},
        {"name": "ChatGPT"},
        {"name": "Intent"}
    ]

    # 导入本地swagger
    app.mount(f'/static', StaticFiles(directory=STATIC_FOLDER), name='static')

    @app.get(app.docs_url, include_in_schema=False)
    async def custom_swagger_ui_html():
        """ 接口文档docs使用本地文件
        :return:
        """
        return get_swagger_ui_html(
            openapi_url=app.openapi_url,
            title=app.title + " - Swagger UI",
            oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
            swagger_favicon_url=f"/static/swagger-ui/favicon-32x32.png",
            swagger_js_url=f"/static/swagger-ui/swagger-ui-bundle.js",
            swagger_css_url=f"/static/swagger-ui/swagger-ui.css",
        )

    @app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
    async def swagger_ui_redirect():
        """
        :return:
        """
        return get_swagger_ui_oauth2_redirect_html()

    @app.get(app.redoc_url, include_in_schema=False)
    async def redoc_html():
        """ 接口文档redoc使用本地文件
        :return:
        """
        return get_redoc_html(
            openapi_url=app.openapi_url,
            title=app.title + " - ReDoc",
            redoc_favicon_url=f"/static/redoc/favicon.png",
            redoc_js_url=f"/static/redoc/bundles/redoc.standalone.js",
        )

    @app.middleware("http")
    async def requests_(request: Request, call_next):
        """
        :param request:
        :param call_next:
        :return:
        """
        response = None
        request_id = ''
        try:
            try:
                # 请求id
                request.state.request_id = request_id = str(uuid4()).replace('-', '')

                message = f'[{request_id}] => request: {request.client.host} "{request.method} {request.url}" ' \
                          f'({request.url.query}) ({request.path_params}) ({request.query_params}) '

                logger.info(message)
            except RuntimeError:
                # todo 上传文件时, request.body()就会报此类型异常，暂时不知道原因，先将此异常做无视处理
                pass

            response = await call_next(request)

        except Exception as ex:
            logger.critical(ex, exc_info=True)
            logger_abnormal.critical(f'[{request_id}] => ' + traceback.format_exc())

        return response

    # 定义接管RequestValidationError的方法
    @app.exception_handler(RequestValidationError)
    @api_common()
    async def validation_exception_handler(request: Request, exc: RequestValidationError):
        """ 自定义接口参数检查
        :param request:
        :param exc:
        :return:
        """
        # 打印request信息
        err = f' {request.client.host} "{request.method} {request.url}" ({request.url.query})'
        try:
            path = f'/{str(request.url).split("?")[0].replace(str(request.base_url), "")}'
            api_param = api_param_map.get(path)
            if not api_param:
                return

            def _get_model_field(_field_names, _model_fields):
                """
                :param _field_names:
                :return:
                """
                _field_name = _field_names[0]
                _model_field_map = {v.name: v for v in _model_fields}
                _model_field: ModelField = _model_field_map.get(_field_name)
                if not _model_field:
                    if len(_model_fields) == 1:
                        # 再往下找一层
                        _model_field = _model_fields[0]
                        if type(_model_field.type_).__name__ != '_UnionGenericAlias' and issubclass(_model_field.type_, BaseModel):
                            _model_field = _get_model_field(_field_names, list(_model_field.type_.__fields__.values()))
                            if not _model_field:
                                return
                        else:
                            return _model_field
                    else:
                        return _model_field

                # 泛型
                if issubclass(type(_model_field.outer_type_), types.GenericAlias):
                    # print(_model_field.outer_type_.__origin__)  # 泛型的格式
                    for v in _model_field.sub_fields:
                        if not issubclass(v.type_, BaseModel):
                            continue
                        _field_names = _field_names[2:]  # 跳过list索引或dict键值
                        _model_field = _get_model_field(_field_names, list(v.type_.__fields__.values()))
                #  过滤typing.Union的场景
                elif type(_model_field.type_).__name__ != '_UnionGenericAlias' and issubclass(_model_field.type_, BaseModel):
                    _field_names = _field_names[1:]
                    _model_field = _get_model_field(_field_names, list(_model_field.type_.__fields__.values()))

                return _model_field

            error = exc.errors()[0]  # 只报一个错误参数
            loc = error.get('loc')
            model_fields = []
            param_type = loc[0]
            if param_type == 'body':
                model_fields = api_param.body_params
            elif param_type == 'query':
                model_fields = api_param.query_params
            elif param_type == 'path':
                model_fields = api_param.path_params

            # 得到该字段参数校验配置
            model_field = _get_model_field(loc[1:], model_fields)

            # 加工参数校验信息提示
            error_desc = get_validation_error_desc(error.get('type'), model_field) or error

            return json_response(code=400, message=error_desc)

        except Exception as ex:
            logger.critical(f'{err} error: {ex}', request.state.request_id, exc_info=True)
            return json_response(code=600, message='参数校验执行异常')

    # view层 api register router
    blue_prints = get_models('project.view', lambda x: isinstance(x, APIRouter))
    for blue_print in blue_prints:
        app.include_router(blue_print)
        # 注册api参数
        register_params(blue_print)

    # 此处修补asyncio以允许嵌套使用asyncio.run和 loop.run_until_complete
    nest_asyncio.apply()

    return app


def get_validation_error_desc(error_type, model_field: ModelField):
    """ 字段格式校验失败后，提示用户自定义的信息
    :param error_type:
    :param model_field:
    :return:
    """
    if not model_field:
        return ''

    desc = PydanticErrors.get_element_value(error_type, 'desc', default=error_type)
    field_info = model_field.field_info
    # 字符串长度小于最小长度
    if error_type == str(PydanticErrors.AnyStrMinLengthError.value):
        desc = desc.format(field_info.title, field_info.min_length)
    # 字符串长度大于最大长度
    elif error_type == str(PydanticErrors.AnyStrMaxLengthError.value):
        desc = desc.format(field_info.title, field_info.max_length)
    # 数字应>最小值
    elif error_type == str(PydanticErrors.NumberNotGtError.value):
        desc = desc.format(field_info.title, field_info.gt)
    # 数字应该>=最小值
    elif error_type == str(PydanticErrors.NumberNotGeError.value):
        desc = desc.format(field_info.title, field_info.ge)
    # 数字应<最小值
    elif error_type == str(PydanticErrors.NumberNotLtError.value):
        desc = desc.format(field_info.title, field_info.lt)
    # 数字应<=最小值
    elif error_type == str(PydanticErrors.NumberNotLeError.value):
        desc = desc.format(field_info.title, field_info.le)
    # 枚举类型判断
    elif error_type == str(PydanticErrors.EnumMemberError.value):
        desc = desc.format(field_info.title)
    else:
        desc = desc.format(field_info.title)

    return desc


def register_params(router: APIRouter):
    """ 根据路由信息注册api参数信息
    :param router:
    :return:
    """
    routes: list[APIRoute] = router.routes
    for route in routes:

        api_param = ApiParam()
        api_param.path = route.path
        dependant: Dependant = route.dependant
        api_param.body_params = dependant.body_params
        api_param.query_params = dependant.query_params
        api_param.path_params = dependant.path_params

        api_param_map[api_param.path] = api_param
